/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package czStvDpds.graphics3D;

import czStvDpds.graphics3D.gui.SuperContainer;
import czStvDpds.input.*;
import czStvDpds.logging.Logging;
import czStvDpds.mapmanagment.AccesibleMap;
import czStvDpds.movement.AbstractPlayer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.ArrayList;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.util.vector.Matrix4f;
import org.lwjgl.util.vector.Vector3f;

/**
 * Hlavní okno aplikace. Běží ve vlastním vlákně.
 *
 * @author Ondra
 */
public class Window extends Thread implements Drawing, InputHandler, GLProperties, LWJGLKeyListener, LWJGLMouseListener, UncaughtExceptionHandler {

    private static Window window;
    private int state;
    private MouseHandler mouseHandler;
    private KeyboardHandler keyboardHandler;
    private DisplayMode displayMode;
    private Scene game, menu;
    private boolean hideCursor = false;
    private boolean localInputAction = false;
    private TextureUnitSettings[] textureUnits;
    private boolean windowClosed = false;
    private ArrayList<GraphicsEndProcessor> endProcessors;
    private Scene[] guiScenes;
    ///////////////////////////////////////////////////////

    /**
     * Vrací vždy stejnou instanci třídy Window;
     *
     * @return
     */
    public static Window getWindow() {
        if (window == null) {
            window = new Window();
        }
        return window;
    }

    /**
     * Vytvoří instacni třídy Window
     */
    private Window() {

        textureUnits = new TextureUnitSettings[32];
        for (int i = 0; i < textureUnits.length; i++) {
            textureUnits[i] = new TextureUnitSettings(i);
        }

        this.endProcessors = new ArrayList();

        game = new GameScene(0, 0, 640, 480, 640.0f / 480.0f, 0.5f, 200, 35);
        guiScenes = new Scene[2];
        guiScenes[0] = new Scene(0, 0, 640, 480, 640.0f / 480.0f, 0.5f, 200f, 0f, 0f, 0f, 640, false, true);
        guiScenes[1] = new Scene(0, 0, 640, 480, 640.0f / 480.0f, 0.5f, 200f, 0f, 0f, 0f, 640, false, true);
        Scene gameMenuScene = this.getGUIScene(SuperContainer.GAME_MENU);
        gameMenuScene.getSceneSettings().setClearColorOnRepaint(false);
        gameMenuScene.getGlSettings().setBlending(true);
        gameMenuScene.getGlSettings().setBlendSourceFactor(GLSettings.BFACTOR_SRC_ALPHA);
        gameMenuScene.getGlSettings().setBlendDestinationFactor(GLSettings.BFACTOR_1MIN_SRC_ALPHA);
        gameMenuScene.getGlSettings().setCullFace(false);
        gameMenuScene.getGlSettings().setDepthTest(false);
        gameMenuScene.setEnabled(false);
        game.addThirdDrawScene(gameMenuScene, 0);

        this.start();
        this.mouseHandler = new MouseHandler();
        this.keyboardHandler = new KeyboardHandler();
    }

    /**
     * Běh vlákna. Mělo by být voláno pouze při vytváření okna
     */
    @Override
    public void run() {
//        Thread.currentThread().setUncaughtExceptionHandler(this);
        displayMode = new DisplayMode(640, 480);
        try {
            Display.setDisplayMode(displayMode);
            Display.create();
            if (game instanceof GameScene) {
                ((GameScene) game).init();
            }
            this.renderingLoop();
            this.windowClosed = true;
            for (GraphicsEndProcessor endProcessor : this.endProcessors) {
                endProcessor.graphicsEnded();
            }
            Display.destroy();
        } catch (LWJGLException ex) {
            ex.printStackTrace();
            Logging.getMainLogger().info(ex.getMessage());
            for (GraphicsEndProcessor endProcessor : this.endProcessors) {
                endProcessor.graphicsEnded();
            }
        }
    }

    /**
     * Vykreslovací smyčka.
     *
     * @throws LWJGLException
     */
    private void renderingLoop() throws LWJGLException {
        while (!Display.isCloseRequested()) {
            if ((!this.keyboardHandler.isRunning()) && Display.isActive()) {
                this.keyboardHandler = this.keyboardHandler.startListening();
            }
            if ((!this.mouseHandler.isRunning()) && Display.isActive()) {
                this.mouseHandler = this.mouseHandler.startListening();
            }
            if (GraphicsIndex.getGLSynchronizer() instanceof Synchronizer) {
                Synchronizer synchronizer = (Synchronizer) GraphicsIndex.getGLSynchronizer();
                synchronizer.doGenerateTextures();
                synchronizer.doGenerateVBOs();
                synchronizer.doChangedTextures();
                synchronizer.doChangeTextures();
                synchronizer.doChangeVBOs();
                synchronizer.doGenerateGlyphs();
            }
            Scene interior = this.getWindowInterior(this.state);
            if (interior != null) {
                if (Display.wasResized()) {
                    interior.resize(0, 0, Display.getWidth(), Display.getHeight(), (float) Display.getWidth() / Display.getHeight());
                }
                interior.draw(null);
            }
            Display.update();
            Display.sync(60);
        }
    }

    /**
     * Vrací "obsah" okna. (menu, hra, ...)
     *
     * @param state
     * @return
     */
    private Scene getWindowInterior(int state) {
        if (state == Drawing.SCENE_GAME) {
            return game;
        } else {
            return null;
        }
    }

    /////////////////////////////implements////////////////////////////////////
    @Override
    public void addLWJGLKeyListener(LWJGLKeyListener listener) {
        keyboardHandler.addLWJGLKeyListener(listener);
    }

    @Override
    public void removeLWJGLKeyListener(LWJGLKeyListener listener) {
        keyboardHandler.removeLWJGLKeyListener(listener);
    }

    @Override
    public ArrayList<LWJGLKeyListener> getLWJGLKeyListeners() {
        return keyboardHandler.getLWJGLKeyListeners();
    }

    @Override
    public void addLWJGLMouseListener(LWJGLMouseListener listener) {
        mouseHandler.addLWJGLMouseListener(listener);
    }

    @Override
    public void removeLWJGLMouseListener(LWJGLMouseListener listener) {
        mouseHandler.removeLWJGLMouseListener(listener);
    }

    @Override
    public ArrayList<LWJGLMouseListener> getLWJGLMouseListeners() {
        return mouseHandler.getLWJGLMouseListeners();
    }

    @Override
    public void setRepeatEvents(boolean enable) {
        this.keyboardHandler.setRepeatEvents(enable);
    }

    @Override
    public boolean areRepeatEvents() {
        return this.keyboardHandler.areRepeatEvents();
    }

    @Override
    public void addPlayer(GraphicalPlayer p) {
        if (this.game instanceof GameScene) {
            ((GameScene) (this.game)).addPlayer(p);
        }
    }

    @Override
    public AccesibleMap getMap() {
        if (this.game instanceof GameScene) {
            return ((GameScene) this.game).getMap();
        } else {
            return null;
        }
    }

    @Override
    public ArrayList<GraphicalPlayer> getPlayers() {
        if (this.game instanceof GameScene) {
            return ((GameScene) (this.game)).getPlayers();
        } else {
            return null;
        }
    }

    @Override
    public void mapChange() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public void playerMove(GraphicalPlayer p) {
    }

    @Override
    public void removePlayer(GraphicalPlayer p) {
        if (this.game instanceof GameScene) {
            ((GameScene) (this.game)).removePlayer(p);
        }
    }

    @Override
    public void setMapData(AccesibleMap map) {
        if (this.game instanceof GameScene) {
            ((GameScene) (this.game)).generateMap(map);
        }
    }

    @Override
    public void setWindowState(int state) {
        this.state = state;
    }

    @Override
    public boolean isCursorHidden() {
        return this.hideCursor;
    }

    @Override
    public void setCursorHidden(boolean hide) {
        this.hideCursor = hide;
        if (hide) {
            Mouse.setGrabbed(true);
        } else {
            Mouse.setGrabbed(false);
        }
    }

    @Override
    public void freeCamera(boolean freeCam) {
        if (game instanceof GameScene) {
            ((GameScene) game).freeCamera(freeCam);
        }
    }

    @Override
    public AbstractPlayer getPlayerWithCamera() {
        if (game instanceof GameScene) {
            return ((GameScene) game).getPlayerWithCamera();
        }
        return null;
    }

    @Override
    public boolean isCameraFree() {
        if (game instanceof GameScene) {
            return ((GameScene) game).isCameraFree();
        }
        return false;
    }

    @Override
    public void locateCameraToPlayer(AbstractPlayer player, float collisionDiameter) {
        if (game instanceof GameScene) {
            ((GameScene) game).locateCameraToPlayer(player, collisionDiameter);
        }
    }

    @Override
    public boolean isWindowClosed() {
        return this.windowClosed;
    }

    @Override
    public void keyAction(LWJGLKeyEvent e) { //Už by se nemělo používat
        if (e.getKey() == LWJGLKeyEvent.KEY_F11 && e.getState() == true) {
        } else if (e.getKey() == LWJGLKeyEvent.KEY_F12 && e.getState() == true) {
            this.localInputAction = !this.localInputAction;
        }
        if (this.localInputAction) {
            if (game instanceof GameScene) {
                ((GameScene) game).keyAction(e);
            }
        }

    }

    @Override
    public void moveCamera(float by) {
        if (game instanceof GameScene) {
            ((GameScene) game).moveCamera(by);
        }
    }

    @Override
    public void rotateCamera(float x, float y) {
        if (game instanceof GameScene) {
            ((GameScene) game).rotateCamera(x, y);
        }
    }

    @Override
    public void mouseAction(LWJGLMouseEvent e) {
        if (this.localInputAction) {
            if (game instanceof GameScene) {
                ((GameScene) game).mouseAction(e);
            }
        }
    }

    @Override
    public TextureUnitSettings getTextureUnitSettings(int number) {
        return textureUnits[number];
    }

    @Override
    public Scene getActualScene() {
        return this.getScene(this.state);
    }

    @Override
    public Scene getScene(int scene) {
        return this.getWindowInterior(scene);
    }

    @Override
    public Vector3f getCameraPosition() {
        if (this.getActualScene() instanceof GameScene) {
            Matrix4f matrix = this.getActualScene().getCamPos();
            return new Vector3f(matrix.m30, matrix.m31, matrix.m32);
        } else {
            return new Vector3f();
        }
    }

    @Override
    public double getPitch() {
        if (this.getActualScene() instanceof GameScene) {
            return ((GameScene) this.getActualScene()).getPitch();
        } else {
            return 0;
        }
    }

    @Override
    public double getYaw() {
        if (this.getActualScene() instanceof GameScene) {
            return ((GameScene) this.getActualScene()).getYaw();
        } else {
            return 0;
        }
    }

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        this.windowClosed = true;
        for (GraphicsEndProcessor endProcessor : this.endProcessors) {
            endProcessor.graphicsEnded();
        }
    }

    @Override
    public ArrayList<GraphicsEndProcessor> getGraphicsEndProcessors() {
        return this.endProcessors;
    }

    @Override
    public void addGraphicsEndProcessor(GraphicsEndProcessor gep) {
        this.endProcessors.add(gep);
    }

    @Override
    public void removeGraphicsEndProcessor(GraphicsEndProcessor gep) {
        this.endProcessors.remove(gep);
    }

    @Override
    public Scene getGUIScene(int sceneName) {
        return this.guiScenes[sceneName];
    }

    @Override
    public void setGUISceneVisibility(int sceneName, boolean visibility) {
        Scene guiScene = this.getGUIScene(sceneName);
        if (guiScene != null) {
            guiScene.setEnabled(visibility);
        }
    }

    @Override
    public boolean getGUISceneVisibility(int sceneName) {
        Scene guiScene = this.getGUIScene(sceneName);
        if (guiScene != null) {
            return guiScene.isEnabled();
        }
        return false;
    }

    @Override
    public void addBulet(GraphicalBullet b) {
        if (this.game instanceof GameScene) {
            ((GameScene) (this.game)).addBullet(b);
        }
    }

    @Override
    public void removeBullet(GraphicalBullet b) {
        if (this.game instanceof GameScene) {
            ((GameScene) (this.game)).removeBullet(b);
        }
    }
}
